import pygame, random, sys

from pygame.locals import *

# 初始化窗口宽高
WINWIDTH = 680
WINHEIGHT = 480
# 初始化推盘行列和空格
BLANK = None
# 颜色预设
DARKGRAY = (60, 60, 60)
WHITE = (255, 255, 255)
YELLOW = (255, 255, 193)
GRAY = (128, 128, 128)
BRIGHTBLUE = (138, 228, 221)
# 颜色变量
BLANKCOLOR = DARKGRAY
MSGCOLOR = WHITE
BTCOLOR = YELLOW
BTTEXTCOLOR = GRAY
BDCOLOR = BRIGHTBLUE
# 静态常量
BLOCKSIZE = 80  # 方块大小
FPS = 60  # 帧率
# 按键事件
UP = 'up'
DOWN = 'down'
LEFT = 'left'
RIGHT = 'right'
NEWGAME = 'newgame'
D_THREE = 'dthree'
D_FOUR = 'dfour'
D_FIVE = 'dfive'
D_RESET = 'dreset'
D_AUTO = 'dauto'

# 随机移动次数
AUTOMOVE = random.randint(50, 100)

# 文本对象创建
def makeText(text, tColor, btColor, top, left):
    textSurf = BASICFONT.render(text, True, tColor, btColor)
    textRect = textSurf.get_rect()
    textRect.topleft = (top, left)
    return textSurf, textRect  # 将字体渲染的Surface对象,Surface对象的矩形属性


# 绘制方块在窗口(tilex,tiley)处,adjx与adjy为偏移量,可选参数默认值为0
def drawTile(tilex, tiley, number, adjx=0, adjy=0):
    left, top = getLeftTopOfTile(tilex, tiley)
    # 绘制方块
    pygame.draw.rect(WINSET, BTCOLOR, (left + adjx, top + adjy, BLOCKSIZE, BLOCKSIZE))
    # 将数字number渲染到方块上
    textSurf = BASICFONT.render(str(number), True, BTTEXTCOLOR)
    # 获取矩形属性
    textRect = textSurf.get_rect()
    # print(textRect)
    textRect.center = left + int(BLOCKSIZE / 2) + adjx, top + int(BLOCKSIZE / 2) + adjy
    # print(textRect.center)
    # print(textRect)
    # 绘制到静态窗口上
    WINSET.blit(textSurf, textRect)


# 计算方块左上角 距离窗口原点纵横坐标距离
def getLeftTopOfTile(tilex, tiley):
    # 先计算左上角方块的距离
    xMargin = int((WINWIDTH - (BLOCKSIZE * COL + (COL - 1))) / 2)  # 199
    yMargin = int((WINHEIGHT - (BLOCKSIZE * ROW + (ROW - 1))) / 2)  # 119
    # print(xMargin,yMargin)
    # print(tilex, tiley) ?
    left = xMargin + (tilex * BLOCKSIZE) + (tilex - 1)
    top = yMargin + (tiley * BLOCKSIZE) + (tiley - 1)
    # print(left, top)
    return left, top


# 静态界面绘制
def darwStaticWin():
    winSet = pygame.display.set_mode((WINWIDTH, WINHEIGHT))
    pygame.display.set_caption('数字华容道')
    image = pygame.image.load('bg.jpg')
    winSet.blit(image, (0, 0))
    # 按钮创建
    new_surf, new_rect = makeText('新游戏', BTTEXTCOLOR, BTCOLOR, WINWIDTH - 100, WINHEIGHT - 40)
    winSet.blit(new_surf, new_rect)
    # 按钮创建
    new_surf3, new_rect3 = makeText('难度:3*3', BTTEXTCOLOR, BTCOLOR, WINWIDTH - 100, WINHEIGHT - 90)
    winSet.blit(new_surf3, new_rect3)
    # 按钮创建
    new_surf4, new_rect4 = makeText('难度:4*4', BTTEXTCOLOR, BTCOLOR, WINWIDTH - 100, WINHEIGHT - 120)
    winSet.blit(new_surf4, new_rect4)
    # 按钮创建
    new_surf5, new_rect5 = makeText('难度:5*5', BTTEXTCOLOR, BTCOLOR, WINWIDTH - 100, WINHEIGHT - 150)
    winSet.blit(new_surf5, new_rect5)
    # 按钮创建
    reset, reset_rect = makeText('重置', BTTEXTCOLOR, BTCOLOR, WINWIDTH - 100, WINHEIGHT - 180)
    winSet.blit(reset, reset_rect)
    # 按钮创建
    auto, auto_rect = makeText('自动完成', BTTEXTCOLOR, BTCOLOR, WINWIDTH - 100, WINHEIGHT - 210)
    winSet.blit(auto, auto_rect)
    return winSet, new_surf, new_rect, new_rect3, new_rect4, new_rect5, reset_rect, auto_rect


# 动态界面绘制
def drawBoard(board, msg, COL, ROW):
    # board 推盘列表
    WINSET.blit(STATICSURF, (0, 0))
    # 如果消息不为空
    if msg:
        msgSurf, msgRect = makeText(msg, DARKGRAY, None, 5, 5)
        # pygame.image.save(msgSurf, 'msg.png')  # 将msgSurf存为名为msg.png的图片
        # imgSurf = pygame.image.load('msg.png')  # 加载图片
        # print(msgRect)  # <rect(5, 5, 87, 28)>  矩形属性
        WINSET.blit(msgSurf, msgRect)

    for i in range(len(board)):  # 3
        for j in range(len(board[0])):  # 3
            if board[i][j]:  # 遍历出不为None的
                # print(board[i][j])
                drawTile(i, j, board[i][j])  # 方块上的数字和所在的行列,绘制方块及数字
    # 绘制外边框
    left, top = getLeftTopOfTile(0, 0)  # 198 118
    width = COL * BLOCKSIZE  # 240
    height = ROW * BLOCKSIZE  # 240
    pygame.draw.rect(WINSET, BDCOLOR, (left - 5, top - 5, width + 11, height + 11), 4)  # 外沿厚度4


# 生成推盘序列
def getStartingBoard(COL, ROW):
    initBoard = []  # 二维列表
    for i in range(COL):  # 列
        i = i + 1
        column = []
        for j in range(ROW):
            column.append(i)
            i += COL  # 按列添加,非顺序添加
        initBoard.append(column)
    initBoard[ROW - 1][COL - 1] = BLANK  # 最后一位添加None空格
    # print(initBoard)
    global AUTOBoard
    AUTOBoard = initBoard
    return initBoard


# 初始化推盘,随机移动numSlides(50~100)次
def generateNewPuzzle(numSlides, COL, ROW):
    mianBoard = getStartingBoard(COL, ROW)  # 获取拼图列表
    drawBoard(mianBoard, '', COL, ROW)
    lastMove = None
    for i in range(numSlides):
        move = getRandomMove(mianBoard, COL, ROW, lastMove)  # 获取随机移动的方向
        slideAnimation(mianBoard, move, '初始化...', int(BLOCKSIZE / 3), COL, ROW)
        makeMove(mianBoard, move, COL, ROW)
        lastMove = move

    global RESETBoard
    RESETBoard = tuple(tuple(tuple(items)) for items in tuple(mianBoard))
    print("执行了一次赋值,RESETBoard:", RESETBoard)
    return mianBoard


# 移动交换元素位置
def makeMove(board, move, COL, ROW):
    blankx, blanky = getBlankPosition(board, COL, ROW)
    # print("换位置了嘛")
    if move == UP:
        board[blankx][blanky], board[blankx][blanky + 1] = board[blankx][blanky + 1], board[blankx][blanky]
    elif move == DOWN:
        board[blankx][blanky], board[blankx][blanky - 1] = board[blankx][blanky - 1], board[blankx][blanky]
    elif move == LEFT:
        board[blankx][blanky], board[blankx + 1][blanky] = board[blankx + 1][blanky], board[blankx][blanky]
    elif move == RIGHT:
        board[blankx][blanky], board[blankx - 1][blanky] = board[blankx - 1][blanky], board[blankx][blanky]


# 移动的动画效果
def slideAnimation(board, direction, msg, animationSpeed, COL, ROW):
    blankx, blanky = getBlankPosition(board, COL, ROW)  # 获取空格的行列坐标
    # 获取被移动的方块所在的行列
    if direction == UP:
        movex = blankx
        movey = blanky + 1
    elif direction == DOWN:
        movex = blankx
        movey = blanky - 1
    elif direction == LEFT:
        movex = blankx + 1
        movey = blanky
    elif direction == RIGHT:
        movex = blankx - 1
        movey = blanky
    drawBoard(board, msg, COL, ROW)
    BASESURF = WINSET.copy()  # 复制此刻背景作为Surface对象
    # 绘制空白块
    moveLeft, moveTop = getLeftTopOfTile(movex, movey)
    pygame.draw.rect(BASESURF, BLANKCOLOR, (moveLeft, moveTop, BLOCKSIZE, BLOCKSIZE))
    # 连续绘制被移动的方块
    # print(animationSpeed)  # 26 方块移动速度
    for i in range(0, BLOCKSIZE, animationSpeed):
        checkForQuit()
        WINSET.blit(BASESURF, (0, 0))
        if direction == UP:
            drawTile(movex, movey, board[movex][movey], 0, -i)
        if direction == DOWN:
            drawTile(movex, movey, board[movex][movey], 0, i)
        if direction == LEFT:
            drawTile(movex, movey, board[movex][movey], -i, 0)
        if direction == RIGHT:
            drawTile(movex, movey, board[movex][movey], i, 0)
        pygame.display.update()  # 更新界面
        FPSCLOCK.tick(FPS)  # 控制帧率


# 判断无效移动
def isValidMove(board, direction, COL, ROW):
    blankx, blanky = getBlankPosition(board, COL, ROW)
    if blankx == 0 and direction == RIGHT:
        return False
    elif blankx == COL - 1 and direction == LEFT:
        return False
    elif blanky == 0 and direction == DOWN:
        return False
    elif blanky == ROW - 1 and direction == UP:
        return False
    else:
        return True


# 获取随机移动的方向
def getRandomMove(board, COL, ROW, lastMove=None):
    validMoves = [UP, DOWN, LEFT, RIGHT]
    if lastMove == UP or not isValidMove(board, DOWN, COL, ROW):
        validMoves.remove(DOWN)
    if lastMove == DOWN or not isValidMove(board, UP, COL, ROW):
        validMoves.remove(UP)
    if lastMove == LEFT or not isValidMove(board, RIGHT, COL, ROW):
        validMoves.remove(RIGHT)
    if lastMove == RIGHT or not isValidMove(board, LEFT, COL, ROW):
        validMoves.remove(LEFT)
    return random.choice(validMoves)


def getBlankPosition(board, COL, ROW):
    for x in range(COL):
        for y in range(ROW):
            if board[x][y] == BLANK:
                return (x, y)


def getSpotClicked(board, x, y):
    for tilex in range(len(board)):
        for tiley in range(len(board[0])):
            left, top = getLeftTopOfTile(tilex, tiley)
            tileRect = pygame.Rect(left, top, BLOCKSIZE, BLOCKSIZE)
            if tileRect.collidepoint(x, y):  # 如果产生碰撞
                return tilex, tiley  # 返回行列
    return None, None


# 获取用户输入
def getInput(mainBoard):
    events = pygame.event.get()
    userInput = None
    for event in events:
        if event.type == MOUSEBUTTONUP:
            # 获取位置关系
            spotx, spoty = getSpotClicked(mainBoard, event.pos[0], event.pos[1])
            # 坐标不在拼图区域,点击的新游戏
            if (spotx, spoty) == (None, None) and NEW_RECT.collidepoint(event.pos):
                userInput = NEWGAME
            elif (spotx, spoty) == (None, None) and THREE.collidepoint(event.pos):
                userInput = D_THREE
            elif (spotx, spoty) == (None, None) and FOUR.collidepoint(event.pos):
                userInput = D_FOUR
            elif (spotx, spoty) == (None, None) and FIVE.collidepoint(event.pos):
                userInput = D_FIVE
            elif (spotx, spoty) == (None, None) and RESET.collidepoint(event.pos):
                # print("你点击重置啦")
                userInput = D_RESET
            elif (spotx, spoty) == (None, None) and AUTO.collidepoint(event.pos):
                # print("你点击了自动")
                userInput = D_AUTO
            else:
                # 如果已完成,点击非选项时不移动
                if mainBoard == getStartingBoard(COL, ROW):
                    break
                # 点击位置是否在BLANK旁边
                blankx, blanky = getBlankPosition(mainBoard, COL, ROW)

                if spotx == blankx + 1 and spoty == blanky:
                    userInput = LEFT  # 设置移动方向
                elif spotx == blankx - 1 and spoty == blanky:
                    userInput = RIGHT
                elif spotx == blankx and spoty == blanky + 1:
                    userInput = UP
                elif spotx == blankx and spoty == blanky - 1:
                    userInput = DOWN
        # 获取键盘输入
        elif event.type == KEYUP:
            if mainBoard == getStartingBoard():
                break
            if event.key in (K_LEFT, K_a):
                userInput = LEFT  # 设置移动方向
            elif event.key in (K_RIGHT, K_d):
                userInput = RIGHT
            elif event.key in (K_UP, K_w):
                userInput = UP
            elif event.key in (K_DOWN, K_s):
                userInput = DOWN
            else:
                break
    return userInput


# 对用户输入进行处理
def processing(userInput, mainBoard, msg, COUNT, COL, ROW):
    # print("RESETBoard-->",RESETBoard)
    if mainBoard == getStartingBoard(COL, ROW):
        msg = '完成,一共走了%d步' % COUNT
    else:
        msg = '通过鼠标移动方块,当前步数:%d' % COUNT + "步"

    if userInput:
        # 功能按钮  新游戏处理:
        if userInput == NEWGAME:
            getStartingBoard(COL, ROW)  # 初始化推盘列表
            mainBoard = generateNewPuzzle(AUTOMOVE, COL, ROW)  # 随机次数移动打乱
            COUNT = 0
        elif userInput == D_THREE:
            COL, ROW = 3, 3
            getStartingBoard(COL, ROW)  # 初始化推盘列表
            mainBoard = generateNewPuzzle(AUTOMOVE, COL, ROW)  # 随机次数移动打乱
            COUNT = 0
        elif userInput == D_FOUR:
            COL, ROW = 4, 4
            getStartingBoard(COL, ROW)  # 初始化推盘列表
            mainBoard = generateNewPuzzle(AUTOMOVE, COL, ROW)  # 随机次数移动打乱
            COUNT = 0
        elif userInput == D_FIVE:
            COL, ROW = 5, 5
            getStartingBoard(COL, ROW)  # 初始化推盘列表
            mainBoard = generateNewPuzzle(AUTOMOVE, COL, ROW)  # 随机次数移动打乱
            COUNT = 0
        elif userInput == D_RESET:
            mainBoard = list(list(list(items)) for items in list(RESETBoard))
            print("发生了调用->RESETBoard", RESETBoard)
            COUNT = 0
        elif userInput == D_AUTO:
            mainBoard = AUTOBoard
            COUNT = 0
            print("自动")
        else:
            # 方块移动
            slideAnimation(mainBoard, userInput, msg, 8, COL, ROW)
            makeMove(mainBoard, userInput, COL, ROW)
            COUNT += 1
    return mainBoard, msg, COUNT, COL, ROW


# 资源回收与程序退出
def terminate():
    pygame.quit()  # 模块卸载
    sys.exit()


# 退出判断
def checkForQuit():
    for event in pygame.event.get(QUIT):  # 获取所有可能会导致退出的事件,存在就执行退出
        terminate()  # 执行退出
    for event in pygame.event.get(KEYUP):  # 按下ESC键退出
        if event.key == K_ESCAPE:
            terminate()
        pygame.event.post(event)  # 发送事件至消息列队


def main():
    # FPSCLOCK Clock对象
    # WINSET 窗体Surface对象
    # STATICSURF 静态窗体Surface对象
    # BASICFONT 字体对象
    # NEW_SURF "新游戏"Surface对象,一张内容为文字的图片  调用字体对象的render()方法,渲染为Surface对象显示
    # NEW_RECT get_rect()方法获取的"新游戏"矩形属性
    global FPSCLOCK, WINSET, STATICSURF, BASICFONT, COL, ROW  # 定义全局变量
    global NEW_SURF, NEW_RECT, COUNT, THREE, FOUR, FIVE, RESET, AUTO

    pygame.init()
    FPSCLOCK = pygame.time.Clock()  # 创建一个Clock对象,并使用变量FPSCLOCK保存
    BASICFONT = pygame.font.Font('STKAITI.ttf', 25)  # 定义返回一个字体对象,并使用变量BASICFONT保存
    WINSET, NEW_SURF, NEW_RECT, THREE, FOUR, FIVE, RESET, AUTO = darwStaticWin()  # 创建静态窗口
    STATICSURF = WINSET.copy()  # 复制一个静态窗口作为底板
    ROW = 3
    COL = 3
    mainBoard = generateNewPuzzle(AUTOMOVE, COL, ROW)  # 初始化列表
    COUNT = 0
    msg = None

    while True:
        FPSCLOCK.tick(FPS)  # 设置帧率
        drawBoard(mainBoard, msg, COL, ROW)  # 动态界面绘制
        pygame.display.update()  # 刷新
        checkForQuit()  # 判断是否终止
        userInput = getInput(mainBoard)  # 获取输入
        mainBoard, msg, COUNT, COL, ROW = processing(userInput, mainBoard, msg, COUNT, COL, ROW)  # 输入处理


if __name__ == '__main__':
    main()
